﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace ALDIOrganizer
{
    public static class DiscrepancyHunter
    {
        internal static Dictionary<Int32, List<Shift>> SplitByStartEnd(List<Shift> pShifts)
        {
            var result = new Dictionary<Int32, List<Shift>>();
            foreach(var shift in pShifts)
            {
                var hash = shift.GetLongHashCode;
                if (result.Keys.Contains(hash))
                {
                    result[hash].Add(shift);
                }
                else
                {
                    var shiftAsList = new List<Shift> {shift};
                    result.Add(
                        hash,
                        shiftAsList);
                }
            }
            return result;
        }

        internal static void MarkOddAsOdd(
            List<Shift> pRotaShifts,
            List<Shift> pTemplateShifts)
        {
            MarkOddAsOdd(
                SplitByStartEnd(pRotaShifts),
                SplitByStartEnd(pTemplateShifts));
        }

        internal static void MarkAllAsNotOdd(List<Shift> pShifts)
        {
            foreach (var shift in pShifts)
            {
                shift.MarkedAsOdd = false;
            }
        }

        internal static void MarkOddAsOdd(
            Dictionary<Int32, List<Shift>> pFirstSet,
            Dictionary<Int32, List<Shift>> pSecondSet)
        {
            var presentInFirstButNotSecond = (from hash in pFirstSet.Keys
                                              where !pSecondSet.Keys.Contains(hash)
                                              select hash).ToArray();
            var presentInSecondButNotFirst = (from hash in pSecondSet.Keys
                                              where !pFirstSet.Keys.Contains(hash)
                                              select hash).ToArray();
            var presentInBothSets = (from hash in pFirstSet.Keys
                                     where pSecondSet.Keys.Contains(hash)
                                     select hash).ToArray();

            foreach (var shift in presentInFirstButNotSecond.SelectMany(hash => pFirstSet[hash]))
            {
                shift.MarkedAsOdd = true;
            }
            foreach (var shift in presentInSecondButNotFirst.SelectMany(hash => pSecondSet[hash]))
            {
                shift.MarkedAsOdd = true;
            }
            foreach (var hash in presentInBothSets)
            {
                TryPermutations(
                    pFirstSet[hash],
                    pSecondSet[hash]);
            }
        }        

        static bool IsMatch(Shift pFirst, Shift pSecond)
        {
            if (pFirst == null ^
                pSecond == null)
            {
                return false;
            }
            if (pFirst == null &&
                pSecond == null) // generally it shouldn't happen
            {
                return true;
            }            
            var rotaShift = (from shift in new [] { pFirst, pSecond }
                             where shift.Parent.IsRota
                             select shift).FirstOrDefault();
            var templateShift = (from shift in new [] { pFirst, pSecond }
                                 where shift.Parent.IsTemplate
                                 select shift).FirstOrDefault();
            if (rotaShift == null ||
                templateShift == null)
            {                
                return false;
            }
            return rotaShift.GetEmployee.HisPosition.Matches(templateShift.JobPosition);
        }

        // among the same ones: 8-15 Mohamm, 8-15 Femi, 8-15 Amina On the rota vs. 8-15 SR, 8-15 SA On the template
        static int TestForDiscrepancies(
            List<Shift> pFirstList,
            List<Shift> pSecondList,
            int[] pSuggestedPairing,
            bool pMarkThem)
        {
            var index = -1;
            var result = 0;

            /* suggested pairing: for example
             *  1   2   3    4  5
             * -1   0   1   -1  2 
             
             now two scenarios!
             1. there'pShift 5 rota Shifts and 3 On the template
             2. there'pShift 3 rota Shifts and 5 On the template */

            var bigger = (pFirstList.Count >= pSecondList.Count) ?
                          pFirstList :
                          pSecondList;
            var smaller = (pFirstList.Count < pSecondList.Count) ?
                           pFirstList :
                           pSecondList;
            
            foreach (var shift in bigger) 
            {
                index++;
                var againstIndex = pSuggestedPairing[index];
                var shiftToCompareAgainst = againstIndex >= 0 ?
                    smaller[pSuggestedPairing[index]] :
                    null;
                
                var match = IsMatch(shift, shiftToCompareAgainst);                
                if (!match)
                {
                    result++;
                }
                if (!pMarkThem)
                {
                    continue;
                }
                shift.MarkedAsOdd = !match;
                if (shiftToCompareAgainst != null)
                {
                    shiftToCompareAgainst.MarkedAsOdd = !match;
                }
            }                
            return result;
        }

        static void TryPermutations(List<Shift> pRotaShifts, List<Shift> pTemplateShifts)
        {
            var rotaShiftsCount = (pRotaShifts != null) ?
                pRotaShifts.Count :
                0;
            var templateShiftsCount = (pTemplateShifts != null) ?
                pTemplateShifts.Count :
                0;
            if (rotaShiftsCount + templateShiftsCount == 0)
            {
                return; // nothing to do
            }            
            var bigger = Math.Max(rotaShiftsCount, templateShiftsCount);
            var smaller = Math.Min(rotaShiftsCount, templateShiftsCount);
            if (Permutator.TooBig(bigger, smaller))
            {
                return;
            }
            if (smaller == 0) // some shifts with no counterparts whatsover = nothing to permutate, just mark them all as odd and go home
            {
                /* and we know for sure that the bigger one is NOT empty, as otherwise
                 * we would have left the method by now (if rota shifts count plus
                 * template shifts count equals zero, we "return": see above) */
                List<Shift> biggerSet = null;
                if (rotaShiftsCount > 0)
                {
                    biggerSet = pRotaShifts;
                }
                if (templateShiftsCount > 0)
                {
                    biggerSet = pTemplateShifts;
                }
                foreach (var shift in biggerSet) // at this point, we know it can't be null (see above), so iterating is safe!
                {
                    shift.MarkedAsOdd = true;
                }
                return;
            }
            
            Permutator.Generate(bigger, smaller);
            var bestResultSoFar = bigger + 1; // any result will be better, obviosuly
            var bestPairing = new int[] { };
                        
            foreach (var pairing in Permutator.PermutationsChart)
            {
                var thisResult = TestForDiscrepancies(
                    pRotaShifts,
                    pTemplateShifts,
                    pairing, false);
                if (thisResult <= bestResultSoFar) // it could be < just the same.  
                {
                    bestPairing = pairing;
                    bestResultSoFar = thisResult;
                }
                if (thisResult == 0) // perfect match. no need to permutate any further
                {
                    break;
                }
            }

            TestForDiscrepancies(
                pRotaShifts,
                pTemplateShifts,
                bestPairing, true);
        }
    }
}
